import {
    provide,
    reactive,
    getCurrentInstance,
    type VNode,
    type InjectionKey,
    type VNodeNormalizedChildren,
    type ComponentPublicInstance,
    type ComponentInternalInstance,
} from 'vue'

// 小程序端不支持从vue导出的isVNode方法，参考uni-mp-vue的实现
function isVNode(value: any): value is VNode {
    return value ? value.__v_isVNode === true : false
}

export function flattenVNodes(children: VNodeNormalizedChildren) {
    const result: VNode[] = []

    const traverse = (children: VNodeNormalizedChildren) => {
        if (Array.isArray(children)) {
            children.forEach((child) => {
                if (isVNode(child)) {
                    result.push(child)

                    if (child.component?.subTree) {
                        result.push(child.component.subTree)
                        traverse(child.component.subTree.children)
                    }

                    if (child.children) {
                        traverse(child.children)
                    }
                }
            })
        }
    }

    traverse(children)

    return result
}

const findVNodeIndex = (vnodes: VNode[], vnode: VNode) => {
    const index = vnodes.indexOf(vnode)
    if (index === -1) {
        return vnodes.findIndex(
            (item) =>
                vnode.key !== undefined &&
                vnode.key !== null &&
                item.type === vnode.type &&
                item.key === vnode.key,
        )
    }
    return index
}

// sort children instances by vnodes order
export function sortChildren(
    parent: ComponentInternalInstance,
    publicChildren: ComponentPublicInstance[],
    internalChildren: ComponentInternalInstance[],
) {
    const vnodes =
        parent && parent.subTree && parent.subTree.children
            ? flattenVNodes(parent.subTree.children)
            : []

    internalChildren.sort(
        (a, b) =>
            findVNodeIndex(vnodes, a.vnode) - findVNodeIndex(vnodes, b.vnode),
    )

    const orderedPublicChildren = internalChildren.map((item) => item.proxy!)

    publicChildren.sort((a, b) => {
        const indexA = orderedPublicChildren.indexOf(a)
        const indexB = orderedPublicChildren.indexOf(b)
        return indexA - indexB
    })
}

export function useChildren<
    // eslint-disable-next-line
    Child extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
    ProvideValue = never,
>(key: InjectionKey<ProvideValue>) {
    const publicChildren: Child[] = reactive([])
    const internalChildren: ComponentInternalInstance[] = reactive([])
    const parent = getCurrentInstance()!

    const linkChildren = (value?: ProvideValue) => {
        const link = (child: ComponentInternalInstance) => {
            if (child.proxy) {
                internalChildren.push(child)
                publicChildren.push(child.proxy as Child)
                sortChildren(parent, publicChildren, internalChildren)
            }
        }

        const unlink = (child: ComponentInternalInstance) => {
            const index = internalChildren.indexOf(child)
            publicChildren.splice(index, 1)
            internalChildren.splice(index, 1)
        }

        provide(
            key,
            Object.assign(
                {
                    link,
                    unlink,
                    children: publicChildren,
                    internalChildren,
                },
                value,
            ),
        )
    }

    return {
        children: publicChildren,
        linkChildren,
    }
}
